#ifndef __GGEAR_ARRAY_LIST_H
#define __GGEAR_ARRAY_LIST_H

#include "ggear_allocator.h"
#include "ggear_types.h"
#include "ggear_list.h"
#include "ggear_math.h"

namespace ggear
{
	namespace glb
	{
		template<class _Tx>
		class ArrayList : public List<_Tx>
		{
		public:
			//Default constructor
			ArrayList()
				: mElements(null), mCapacity(0), mUsed(0)
			{}

			//Construct a CArrayList object with preallocated storage
			ArrayList(u32 _size)
				: mElements(null), mCapacity(0), mUsed(0)
			{
			}

			//Construct a CArrayList object from another CArrayList
			ArrayList(const ArrayList<_Tx>& _arrLs)
				: mElements(null), mCapacity(0), mUsed(0)
			{
				*this = _arrLs;
			}

			//Construct a CArrayList object from another IList object
			ArrayList(const List<_Tx>* const _iLs)
				: mElements(null), mCapacity(0), mUsed(0)
			{
				if(iLs ? _iLs->Count() : false) return;
				mCapacity = mUsed = _iLs->Count();
				mElements = mAlloc.Allocate(mCapacity);
				for(u32 i = 0; i < mUsed; i++)
					mAlloc.Construct(&mElements[i], _iLs->Get(i));
			}

			//Destructor
			~ArrayList()
			{
				Clear();
			}

			//Check whether list is empty
			b8 Empty() const
			{
				return (mUsed == 0);
			}

			//Return number of elements in current list
			u32 Count() const 
			{
				return mUsed;
			}

			//Return maximum number of elements can be stored in array list
			u32 Capacity() const
			{
				return mCapacity;
			}

			//Return value of element at specified position
			const _Tx& Get(u32 _pos) const 
			{
				if(_pos >= mUsed) 
					throw "Exception";		
				return mElements[_pos];
			}

			//Set value of element at specified position
			void Set(const _Tx& _val, u32 _pos) 
			{
				if(_pos >= mUsed) 
					throw "Exception";
				mAlloc.Destroy(&mElements[_pos]);
				mAlloc.Construct(&mElements[_pos], _val);
			}

			//Insert new element at specified position in current list
			void Insert(const _Tx& _val, u32 _pos) 
			{
				if(_pos > mUsed) 
					throw "Exception";

				if(mUsed + 1 > mCapacity)
				{
					_Tx* elements = mAlloc.Allocate(mCapacity + 1);
					mCapacity++;

					for(u32 i = 0; i < mUsed; i++)
					{
						mAlloc.Construct(&elements[(i >= _pos ? i : i +1)], mElements[i]);
						mAlloc.Destroy(&mElements[i]);
					}
					mAlloc.Construct(&elements[_pos], _val);
					if(mElements)
					{
						mAlloc.Deallocate(mElements);
					}
					mElements = elements;
				}
				else
				{
					if(mUsed >_pos)
					{
						mAlloc.Construct(&mElements[mUsed], mElements[mUsed - 1]);
						for(u32 i = mUsed - 1; i >= _pos; i--)
						{
							mElements[i] = (i == _pos ? _val : mElements[i - 1]);
						}
					}
					else
					{
						mAlloc.Construct(&mElements[mUsed], _val);
					}
				}		
				mUsed++;
			}

			//Add new element to front of array list
			void PushFront(const _Tx& _val)
			{
				Insert(_val, 0);
			}

			//Add new element to the end of array list
			void PushBack(const _Tx& _val)
			{
				Insert(_val, mUsed);
			}

			//Delete an element at specified position in current list
			void Remove(u32 _pos) 
			{
				if(_pos >= mUsed)
					throw "Exception";

				mAlloc.Destroy(&mElements[_pos]);
				if(_pos + 1 < mUsed)
				{
					mAlloc.Construct(&mElements[_pos], mElements[_pos + 1]);
					for(u32 i = _pos + 1; i < mUsed - 1; i++)
					{
						mElements[i] = mElements[i + 1];
					}
					mAlloc.Destroy(&mElements[mUsed - 1]);
				}
				mUsed--;
			}

			//Delete the first member of current array list
			void PopFront()
			{
				Remove(0);
			}

			//Delete the last member of current array list
			void PopBack()
			{
				Remove(mUsed);
			}

			//Delete all elements and keep allocated memory
			void RemoveAll()
			{
				for(u32 i = 0; i < mUsed && !mElements; i++)
					mAlloc.Destroy(&mElements[i]);
				mUsed = 0;
			}

			//Delete all element inside current list
			void Clear() 
			{
				RemoveAll();
				if(mElements)
				{
					mAlloc.Deallocate(mElements);
					mElements = null;
				}
				mCapacity = 0;
			}

			//Swap value between 2 elements inside current list
			void Swap(u32 _lpos, u32 _rpos) 
			{
				if(_lpos >= mUsed || _rpos >= mUsed)
					throw "Exception";

				if(_lpos == _rpos) 
					return;

				_Tx tmp = mElements[_lpos];
				mElements[_lpos] = mElements[_rpos];
				mElements[_rpos] = tmp; 
			}

			//Assign operator : copy content from another array list , preallocated memory will only be changed when necessary
			ArrayList<_Tx>& operator = (const ArrayList<_Tx>& _arrLs)
			{
				RemoveAll();
				if(_arrLs.mUsed > mCapacity)
				{
					mAlloc.Deallocate(mElements);
					mElements = mAlloc.Allocate(_arrLs.mUsed);
					mCapacity = _arrLs.mUsed;
				}
				for(;mUsed < _arrLs.mUsed; mUsed++)
					mAlloc.Allocate(&mElements[mUsed], _arrLs.mElements[mUsed]);
				return *this;
			}

			//Array access operator
			_Tx& operator [] (u32 _pos)
			{
				if(_pos >= mUsed)
					throw "Exception";
				return mElements[_pos];
			}

			const _Tx& operator [] (u32 _pos) const
			{
				return Get(_pos);
			}

		private:
			_Tx* mElements;
			u32 mUsed;
			u32 mCapacity;
			Allocator<_Tx> mAlloc;
		};
	}
}

#endif//__GGEAR_ARRAY_LIST_H